package atessitura;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;


import midiwork.MidiNote;
import midiwork.MidiTrack;
import midiwork.NoteScale;


public class Guitar
{
	public static final int NUM_FRETS=24;
	private int[] tuning;
	private MidiTrack track;
	private int numOfGuitarNotes;
	private ArrayList<GuitarNote>[] notes;
	private ArrayList <GuitarNote> guitarNotes;
	private ArrayList <ArrayList> chords;
	private Chords chordData;
	
	public final int[] standard={64,59,55,50,45,40};
	//private int tickLength;
	
	public Guitar(MidiTrack trck)
	{
		track=trck;
		tuning=standard;
	}
	
	public Guitar(MidiTrack trck, boolean suggested) throws UnplayableException
	{
		notes=null;
		
		track=trck;
		tuning=standard;
		guitarNotes=new ArrayList<GuitarNote>();
		chords = new ArrayList<ArrayList> (); // this will hold all chords, one array list = 1 chord
		
		if (suggested)
		{
			String suggest=this.convertIntToTuningString(this.suggestTuning());
			
			setTuning(suggest);
			//setTuning(this.suggestTuning());
			//setTuning("(E A D G B E)");
			//setTuning("(E B G D A E)");
		}
		
	}
	
	public Guitar(ArrayList<GuitarNote>[] importedNotes)
	{
		notes=importedNotes;
	}
	
	public void setTuning(String tunePicked) throws UnplayableException
	{
		tuning=stringToTuning(tunePicked.substring(tunePicked.indexOf('(')+1,tunePicked.indexOf(')')));
		//track=trck;
		MidiNote lastNote=track.getNotes().get(track.getNotes().size()-1);
		int tickLength=lastNote.getEndTime();
		notes=new ArrayList[tickLength+1];
		ArrayList<MidiNote>[] midiNotes= setUpMidiNotes(tickLength+1);
		
		
		
		HashMap<MidiNote, GuitarNote> map=recursiveSet(0,new HashMap<MidiNote, GuitarNote>());
		
		
		numOfGuitarNotes=map.keySet().size();
		
		for (MidiNote note:map.keySet())
		{
			if (notes[note.getStartTime()]==null)
			{
				notes[note.getStartTime()]=new ArrayList<GuitarNote>();
			}
			notes[note.getStartTime()].add(map.get(note));
		}
		getGuitarNotes (map); //adds guitar notes to ArrayList guitarNotes
		setChords();
		
		chordData= new Chords (tuning); // holds database
		for (ArrayList <GuitarNote> list: chords)
		{
			list = chordData.convertNotes(list); //fix up our chords
			
		}
		printGuitarNotes();
	}
	
	public void getGuitarNotes (HashMap <MidiNote, GuitarNote> map)
	{
		Set <MidiNote> keyset = map.keySet();
		for (MidiNote n: keyset)
		{
			guitarNotes.add(map.get(n));
		}
	}

	public void setChords ()
	{	
		for (int tick=0;tick<notes.length;tick++)
		{
			if (notes[tick]!=null && notes[tick].size()!=1)
			{
				chords.add(notes[tick]);
			}
		}
	}
		
	public HashMap<MidiNote, GuitarNote> recursiveSet(int i, HashMap<MidiNote, GuitarNote> map) throws UnplayableException
	{
		ArrayList<MidiNote> allNotes=track.getNotes();
		if (i==allNotes.size())
		{
			return map;
		}
		ArrayList<MidiNote> currentlyPlayed=getAllNotesCurrentlyPlayed(allNotes.get(i));
		ArrayList<GuitarNote> options=this.getGuitarNoteOptions(allNotes.get(i));
		boolean isNotConflicted=true;
		for (GuitarNote option:options)
		{
			for (MidiNote current:currentlyPlayed)
			{
				GuitarNote currentG=map.get(current);
				if (currentG!=null)
				{
					if (currentG.getString()==option.getString())
					{
						isNotConflicted=false;
						break;
					}
				}
			}
			if (isNotConflicted)
			{
				map.put(allNotes.get(i), option);
				break;
			}
			isNotConflicted=true;
		}
		return recursiveSet(i+1,map);
	}
	
	public ArrayList<MidiNote> getAllNotesCurrentlyPlayed(MidiNote note)
	{
		ArrayList<MidiNote> currentlyPlayed=new ArrayList<MidiNote>();
		int endTime=note.getEndTime();
		int startTime=note.getStartTime();
		ArrayList<MidiNote> allNotes=track.getNotes();
		for (MidiNote current:allNotes)
		{
			if((current.getEndTime()<=endTime && current.getEndTime()>startTime) || (current.getStartTime()>=startTime && current.getStartTime()<endTime))
			{
				currentlyPlayed.add(current);
			}
		}
		return currentlyPlayed;
	}
	
	public ArrayList<MidiNote>[] setUpMidiNotes(int length)
	{
		
		ArrayList<MidiNote>[] midiNotes=new ArrayList[length];
		ArrayList<MidiNote> trackNotes=track.getNotes();
		for (MidiNote note:trackNotes)
		{
			int tick=note.getStartTime();
			if (midiNotes[tick]==null)
			{
				midiNotes[tick]=new ArrayList<MidiNote>();
			}
			midiNotes[tick].add(note);
		}
		return midiNotes;
	}
	
	public int[] stringToTuning(String tunePicked)
	{
		String[] split=tunePicked.split(" ");
		int[] standard={64-NoteScale.E,59-NoteScale.B,55-NoteScale.G,50-NoteScale.D,45-NoteScale.A,40-NoteScale.E};
		
		split=reverseStringArray(split);
		for (int i=0;i<split.length;i++)
		{
			if (split[i].equals("A"))
			{
				standard[i]+=NoteScale.A;
			}
			else if (split[i].equals("A#"))
			{
				standard[i]+=NoteScale.Asharp;
			}
			else if (split[i].equals("Bb"))
			{
				standard[i]+=NoteScale.Bflat;	
			}
			else if (split[i].equals("B"))
			{
				standard[i]+=NoteScale.B;
			}
			else if (split[i].equals("C"))
			{
				standard[i]+=NoteScale.C;
			}
			else if (split[i].equals("C#"))
			{
				standard[i]+=NoteScale.Csharp;
			}
			else if (split[i].equals("Db"))
			{
				standard[i]+=NoteScale.Dflat;
			}
			else if (split[i].equals("D"))
			{
				standard[i]+=NoteScale.D;
			}
			else if (split[i].equals("D#"))
			{
				standard[i]+=NoteScale.Dsharp;
			}
			else if (split[i].equals("Eb"))
			{
				standard[i]+=NoteScale.Eflat;
			}
			else if (split[i].equals("E"))
			{
				standard[i]+=NoteScale.E;
			}
			else if (split[i].equals("F"))
			{
				standard[i]+=NoteScale.F;
			}
			else if (split[i].equals("F#"))
			{
				standard[i]+=NoteScale.Fsharp;
			}
			else if (split[i].equals("Gb"))
			{
				standard[i]+=NoteScale.Gflat;
			}
			else if (split[i].equals("G"))
			{
				standard[i]+=NoteScale.G;
			}
			else if (split[i].equals("G#"))
			{
				standard[i]+=NoteScale.Gsharp;
			}
			else if (split[i].equals("Ab"))
			{
				standard[i]+=NoteScale.Aflat;
			}
		}
		return standard;
	}
	
	public int[] suggestTuning()
	{
		ArrayList<MidiNote> midiNotes=track.getNotes();
		MidiNote lowest=null;
		MidiNote highest=null;
		for (MidiNote note:midiNotes)
		{
			if (lowest==null || lowest.getNumber()>note.getNumber())
			{
				lowest=note;
			}
			if (highest==null || highest.getNumber()<note.getNumber())
			{
				highest=note;
			}
		}
		int[] suggested=new int[6];
		for (int i=0;i<6;i++)
		{
			suggested[i]=standard[i];
		}
		if (lowest.getNumber()>=standard[5] && highest.getNumber()<=getStringLimit(0))
		{
			return suggested;
		}
		if (lowest.getNumber()>=39 && highest.getNumber()<=getStringLimit(0)-1)
		{
			return transpose(suggested,-1);
		}
		if (lowest.getNumber()>=38 && highest.getNumber()<=getStringLimit(0))
		{
			suggested[5]-=2;
			return suggested;
		}
		if (lowest.getNumber()>=36 && highest.getNumber()<=getStringLimit(0)-2)
		{
			suggested=transpose(suggested,-2);
			suggested[5]-=2;
			return suggested;
		}
		if (lowest.getNumber()>=34 && highest.getNumber()<getStringLimit(0)-4)
		{
			suggested=transpose(suggested,-4);
			suggested[5]-=2;
			return suggested;
		}
		if (lowest.getNumber()>=32 && highest.getNumber()<getStringLimit(0)-6)
		{
			suggested=transpose(suggested,-6);
			suggested[5]-=2;
			return suggested;
		}
		if (lowest.getNumber()<=42 && highest.getNumber()<getStringLimit(0)+2)
		{
			return transpose(suggested,2);
		}
		if (lowest.getNumber()<=44 && highest.getNumber()<getStringLimit(0)+4)
		{
			return transpose(suggested,4);
		}
		return suggested;
	}
	
	public ArrayList<GuitarNote> getGuitarNoteOptions(MidiNote note)
	{
		return getGuitarNoteOptions(note.getNumber(), note.getDuration(), note.getStartTime());
	}
	
	public ArrayList<GuitarNote> getGuitarNoteOptions(int note, int duration, int startTime)
	{
		ArrayList<GuitarNote> options=new ArrayList<GuitarNote>();
		for (int i=0;i<6;i++)
		{
			if(note>=tuning[i] && note<=getStringLimit(i))
			{
				//System.out.println("testoutput: note="+note+" tuning["+i+"]="+tuning[i]);
				options.add(new GuitarNote(i,note-tuning[i], duration, startTime, tuning));
			}
		}
		//System.out.println(note+ " options: "+options);
		return orderByLowestFret(options);
	}
	
	public ArrayList<GuitarNote> orderByLowestFret(ArrayList<GuitarNote> arr)
	{
		ArrayList<GuitarNote> inOrder=new ArrayList<GuitarNote>();
		while(arr.size()>0)
		{
			int lowestFret=0;
			for (int i=1;i<arr.size();i++)
			{
				if (arr.get(i).getFret()<arr.get(lowestFret).getFret())
				{
					lowestFret=i;
				}
			}
			inOrder.add(arr.get(lowestFret));
			arr.remove(lowestFret);
		}
		return inOrder;
	}
	
	public int getNoteFromGuitar(GuitarNote note)
	{
		return tuning[note.getString()] +note.getFret();
	}
	
	public int getStringLimit(int string)
	{
		return tuning[string]+NUM_FRETS;
	}
	
	public boolean notesOverlap(MidiNote note1, MidiNote note2)
	{
		if(note1.getStartTime()<=note2.getStartTime())
		{
			if (note1.getEndTime()<=note2.getStartTime())
			{
				return true;
			}
			return false;
		}
		else
		{
			if (note2.getEndTime()<=note1.getStartTime())
			{
				return true;
			}
			return false;
		}
	}
	
	public boolean isChord (MidiNote note1, MidiNote note2)
	{
		if (note1.getStartTime() == note2.getStartTime())
		{
			return true;
		}
		return false;
	}
	
	public int[] transpose(int[] tuning, int trans)
	{
		for (int i=0;i<tuning.length;i++)
		{
			tuning[i]+=trans;
		}
		return tuning;
	}
	
	public int[] reverseIntArray(int[] toReverse)
	{
		int[] returnThis=new int[toReverse.length];
		for (int i=0;i<toReverse.length;i++)
		{
			returnThis[i]=toReverse[toReverse.length-1-i];
		}
		return returnThis;
	}
	
	public String[] reverseStringArray(String[] toReverse)
	{
		String[] returnThis=new String[toReverse.length];
		for (int i=0;i<toReverse.length;i++)
		{
			returnThis[i]=toReverse[toReverse.length-1-i];
		}
		return returnThis;
	}
	
	public void printGuitarNotes()
	{
		//System.out.println("[GUITAR PRINT number of GuitarNotes="+numOfGuitarNotes+", number of original MidiNotes="+track.getNotes().size()+", loss="+(track.getNotes().size()-numOfGuitarNotes));
		for (int tick=0;tick<notes.length;tick++)
		{
			if (notes[tick]!=null)
			{
				for (int note=0;note<notes[tick].size();note++)
				{
					GuitarNote currentGNote=notes[tick].get(note);
					//System.out.println(currentGNote+"   note="+(tuning[currentGNote.getString()]+currentGNote.getFret()));
				}
			}
		}
		//System.out.println("]");
	}
	
	public String convertIntToTuningString(int[] tuning)
	{
		String converted="(";
		tuning=this.reverseIntArray(tuning);
		for (int i=0;i<tuning.length;i++)
		{
			MidiNote current=new MidiNote(0,0,tuning[i], 0);
			converted+=current.getScaleNote();
			if (i<tuning.length-1)
			{
				converted+=" ";
			}
		}
		return converted+")";
	}
	
	public ArrayList<GuitarNote>[] getNotes()
	{
		return notes;
	}
	
	public int[] getTuning()
	{
		return tuning;
	}
	
	
	public void setTuning(int[] newTune)
	{
		tuning=newTune;
	}
	
	public int getNumTicks()
	{
		return notes.length;
	}
	
	public void moveAllNotes(int move)
	{
		/*try{
			for (int note=0;note<track.getNotes().size();note++)
			{
				MidiNote theNote=track.getNotes().get(note);
				theNote.setNumber(theNote.getNumber()+move);
			}
			setTuning(convertIntToTuningString(tuning));
		}catch(Exception e)
		{
			
		}*/
		
	}
	
	public void octaveUp()
	{
		moveAllNotes(17);
	}
	
	public void octaveDown()
	{
		moveAllNotes(-17);
	}
}
